#!/bin/bash
#
# Companion password manager for quickpass.
#
# A few things worth noting before you decide to use this program:
#
#  - An accredited security audit has NOT been performed on this program yet.
#  - This program relies on GnuPG and a PGP key to perform encryption and
#    decryption, so make sure you use a strong (>4000 bits) key!
#  - Unlike KeePass, this program does NOT encrypt the RAM it uses, nor does
#    it encrypt the entire database. Decide for yourself if this is enough.
#
# Inspired by alimiracles `pm` file.
#
#    Copyright (c) 2016 ali abdul ghani <alimiracle@riseup.net>
#    Copyright (c) 2016 kzimmermann <kzimmermann@vivaldi.net>
#
#    This file is part of quickpass 
#
#    quickpass is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#    quickpass is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    You should have received a copy of the GNU General Public License
#    along with quickpass.  If not, see <http://www.gnu.org/licenses/>.
#

#-- predefined variables --#

VAULT="$HOME/.quickpassdb"

# change this to a private key available in your keyring.
# you can use any format that gpg accepts, like an email address or fingerprint
RECIPIENT=""

#-- /predefined variables --#

#-- Function definitions --

error() {
    # shorthand for a quit-due-to-error message.
    echo "Error: $1"
    exit 1
}

helper() {
    cat <<EOF
$(basename $0): a password manager for quickpass.
USAGE $(basename $0) COMMAND [ARGUMENT]

COMMANDS are:
 -h, --help: this help message.
 -n, --new [SERVICE NAME]: creates a new entry in the vault using quickpass.
 -r, --read [SERVICE NAME]: decrypts and reads the entry for [SERVICE NAME].
 -d, --delete [SERVICE NAME]: deletes entry for [SERVICE NAME].
 -l, --list: lists all available password entries.
 -L, --length [N]: generates passwords of N characters long instead of 32.

EOF
if [[ -n "$RECIPIENT" ]]
then
    cat <<EOF
The current recipient (user) is: $RECIPIENT.
$(gpg --list-keys $RECIPIENT)
EOF
else
    echo "Error: you have not specified a recipient (vault user) yet."
    echo "Please change the line RECIPIENT=\"\" to your gpg key to use it."
    exit 1
fi
}

create_db() {
    # create a db if there's none available.
    if [[ ! -f "$VAULT" ]]
    then
        sqlite3 "$VAULT" "
        CREATE TABLE IF NOT EXISTS entries (
            service TEXT PRIMARY KEY,
            password TEXT
        )"
        echo "New password vault created at $VAULT"
    else
        echo "A password vault already exists at $VAULT."
    fi
}

new_entry() {
    # add a new row in the password database.
    service="$1"
    if [[ -z "$LENGTH" ]]
    then
        encrypted=$(quickpass -p | gpg -a -e -r $RECIPIENT)
    else
        encrypted=$(quickpass -p -l $LENGTH | gpg -a -e -r $RECIPIENT)
    fi
    sqlite3 "$VAULT" "
    INSERT INTO entries (service, password)
    VALUES ('$service', '$encrypted')" || error "could not insert into vault..."
    if [[ -z "$LENGTH" ]]
    then
        echo "Created 1 new password entry for service '$service'"
    else
        echo "Created 1 new password $LENGTH characters long for service '$service'"
    fi
}

delete_entry() {
    # remove a row from the database.
    sanity=$(sqlite3 "$VAULT" "
            SELECT service FROM entries
            WHERE service = '$1'
    ")
    [[ -z "$sanity" ]] && error "entry '$1' not found"

    printf "Would you like to delete entry '$1'? (y/n) "
    read decision
    if [[ "$decision" == "y" ]]
    then
        sqlite3 "$VAULT" "
            DELETE FROM entries
            WHERE service = '$1'
        " 
        echo "Entry '$1' deleted."
    else
        echo "Aborted."
        exit 1
    fi
}

read_entry() {
    # return a decrypted row from the database
    encrypted=$(sqlite3 "$VAULT" "
        SELECT password FROM entries
        WHERE service = '$1'
        ")
    if [[ -n "$encrypted" ]]
    then
        echo "Unlock your key to obtain the password"
        printf %b "$encrypted" | gpg --decrypt | xsel -i -b
        echo "Password copied to the clipboard."
        printf "Clearing automatically in "
        for i in $(seq 1 10)
        do
            printf "$(expr 11 - $i)... "
            sleep 1
        done
        echo $RANDOM | xsel -i -b
        echo "** Cleared. **"
    else
        echo "Entry $1 doesn't exist in the vault."
        echo "Use '--list' to see all available entries."
        exit 1
    fi
}

list_entry() {
    # list all available entries from the database:
    echo "You have the following password entries available here:"
    sqlite3 "$VAULT" "SELECT service FROM entries"
}

#-- /Function definitions --

[[ -z $(which sqlite3) ]] && error "sqlite3 not found."
[[ -z $(which gpg) ]] && error "gpg not found."
[[ -z "$RECIPIENT" ]] && helper
[[ ! -f "$VAULT" ]] && create_db

# We can only perform an action at a time. Sorry.
action=""

while [[ -n "$1" ]]
do
    case "$1" in
        "-h" | "--help" ) 
            helper
            exit 0
        ;;
        "-n" | "--new" )
            [[ -n "$action" ]] && error "one action at a time only."
            action="new"
            shift
            [[ -z "$1" ]] && error "Missing arguments. See --help"
            token="$1"
        ;;
        "-r" | "--read" )
            [[ -n "$action" ]] && error "one action at a time only."
            action="read"
            shift
            [[ -z "$1" ]] && error "Missing arguments. See --help"
            token="$1"
        ;;
        "-d" | "--delete" )
            [[ -n "$action" ]] && error "one action at a time only."
            action="delete"
            shift
            [[ -z "$1" ]] && error "Missing arguments. See --help"
            token="$1"
        ;;
        "-l" | "--list" )
            list_entry
            exit 0
        ;;
        "-L" | "--length" )
            shift
            [[ -z "$1" ]] && error "Missing arguments. See --help"
            LENGTH="$1"
        ;;
        * )
            echo "Unknown option '$1'"
            exit 1
        ;;
    esac
    shift
done

# No argument?
if [[ -z "$action" ]]
then
    echo "No action specified to proceed."
    helper
    exit 1
fi

case "$action" in
    "new" ) 
        new_entry "$token"
    ;;
    "read" ) 
        read_entry "$token"
    ;;
    "delete" ) 
        delete_entry "$token"
    ;;
esac
